// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package types

import (
	"cmd/compile/internal/base"
	"cmd/internal/obj"
	"cmd/internal/src"
	"unicode"
	"unicode/utf8"
)

// Sym表示分段（pkg，name）命名空间中的对象名称。
// 最常见的是，这是一个Go标识符，用于命名包中声明的对象，
// 但符号也用于命名内部合成对象。
// None
// 作为例外，导出的字段和方法名称使用Sym
// 与localpkg关联，而不是与声明它们的包关联。这
// 允许在以下情况下使用Sym指针相等性测试Go标识符的唯一性：
// 处理选择器表达式。
// None
// 理想情况下，Sym应用于表示Go语言结构，
// 而cmd/internal/obj.LSym用于表示发出的工件。
// None
// 注意：在实践中，事情可能比上面的描述更混乱
// 出于各种原因（历史原因、便利性）。
type Sym struct {
	Linkname string // 链接名称

	Pkg  *Pkg
	Name string // 对象名

	// Def、Block和Lastlineno由Pushdcl/Popdcl保存和恢复。

	// 此符号所属的唯一ONAME、OTYPE、OPACK或OLITERAL节点
	// 在当前范围内绑定到。（编译器的大部分部分应该
	// 宁愿直接传递节点，而不是依赖此字段。）
	Def        Object
	Block      int32    // 要捕获重新声明的块编号
	Lastlineno src.XPos // 诊断的最后声明

	flags bitset8
}

const (
	symOnExportList = 1 << iota // 已添加到exportlist（无需再次添加）
	symUniq
	symSiggen // 已生成类型符号
	symAsm    // 在asmlist上，用于写入-asmhdr
	symFunc   // 功能符号
)

func (sym *Sym) OnExportList() bool { return sym.flags&symOnExportList != 0 }
func (sym *Sym) Uniq() bool         { return sym.flags&symUniq != 0 }
func (sym *Sym) Siggen() bool       { return sym.flags&symSiggen != 0 }
func (sym *Sym) Asm() bool          { return sym.flags&symAsm != 0 }
func (sym *Sym) Func() bool         { return sym.flags&symFunc != 0 }

func (sym *Sym) SetOnExportList(b bool) { sym.flags.set(symOnExportList, b) }
func (sym *Sym) SetUniq(b bool)         { sym.flags.set(symUniq, b) }
func (sym *Sym) SetSiggen(b bool)       { sym.flags.set(symSiggen, b) }
func (sym *Sym) SetAsm(b bool)          { sym.flags.set(symAsm, b) }
func (sym *Sym) SetFunc(b bool)         { sym.flags.set(symFunc, b) }

func (sym *Sym) IsBlank() bool {
	return sym != nil && sym.Name == "_"
}

// 不推荐：不应直接使用此方法。相反，使用
// 直接返回链接器符号的高级抽象
// 对于命名对象。例如，改为reflectdata.TypeLinksym（t）
// 反射数据的类型。TypeSym（t）.Linksym（）。
func (sym *Sym) Linksym() *obj.LSym {
	abi := obj.ABI0
	if sym.Func() {
		abi = obj.ABIInternal
	}
	return sym.LinksymABI(abi)
}

// 不推荐：不应直接使用此方法。相反，使用
// 直接返回链接器符号的高级抽象
// 对于命名对象。例如，（*ir.Name）.LinksymABI（abi）代替
// of（*ir.Name）.Sym（）.LinksymABI（abi）。
func (sym *Sym) LinksymABI(abi obj.ABI) *obj.LSym {
	if sym == nil {
		base.Fatalf("nil symbol")
	}
	if sym.Linkname != "" {
		return base.Linkname(sym.Linkname, abi)
	}
	return base.PkgLinksym(sym.Pkg.Prefix, sym.Name, abi)
}

// Less报告符号a是否在符号b之前排序。
// None
// 符号在非导出之前按顺序导出，然后按名称和顺序导出
// 最后（对于非导出符号）按包高度和路径。
// None
// 按包装高度订购对于建立一致的
// 对拼写相同但来自的非导出名称进行排序
// 不同的包装。我们不一定知道未来的道路
// 正在编译的包，但根据定义，它将有一个高度
// 大于编译单元中看到的任何其他包。
// 有关更多背景信息，请参阅第24693期。
func (a *Sym) Less(b *Sym) bool {
	if a == b {
		return false
	}

	// 在未导出之前导出符号。
	ea := IsExported(a.Name)
	eb := IsExported(b.Name)
	if ea != eb {
		return ea
	}

	// 按名称订购，然后（对于非导出名称）按包装订购
	// 高度和路径。
	if a.Name != b.Name {
		return a.Name < b.Name
	}
	if !ea {
		if a.Pkg.Height != b.Pkg.Height {
			return a.Pkg.Height < b.Pkg.Height
		}
		return a.Pkg.Path < b.Pkg.Path
	}
	return false
}

// IsExported报告名称是否为导出的Go符号（即，
// 是否以大写字母开头）。
func IsExported(name string) bool {
	if r := name[0]; r < utf8.RuneSelf {
		return 'A' <= r && r <= 'Z'
	}
	r, _ := utf8.DecodeRuneInString(name)
	return unicode.IsUpper(r)
}
